序言
作为开发人员,我们经常需要和第三方公司进行接口联调工作。
要想联调成功,除了需要双方按照约定的接口文档进行代码开发外,还有个要求——双方网络得保持畅通。
然鹅,网络这个家伙有时候非常调皮,偏偏喜欢搞事情,emmm,为了甩锅(啊不,为了定位问题),我们可以使用抓包工具去观察下数据包的出入流量情况,来确定到底是哪一方的问题。
tcpdump
是 Linux 自带的一个不错的抓包工具,那么,就跟随这篇文章来学习怎么使用它吧!
基础用法
监听第一块网卡往来的数据包
不指定任何参数使用该命令,可以监听第一块网卡上往来的数据包。
1 | tcpdump |
监听特定网卡往来的数据包
由于主机上大多不止一块网卡,所以经常需要监听特定网卡往来的数据包,此时增加-i
参数即可指定特定网卡:
1 | tcpdump -i en0 |
监听特定主机往来的数据包
有时候,我们想监听本机跟其他某个主机之间往来的通信包,此时增加host
参数指定对应主机即可:
1 | tcpdump host 42.254.38.55 |
监听来自特定主机的数据包
有时候,我们只想监听来自特定主机的数据包,此时在host
参数上新增src
即可:1
tcpdump src host hostname
监听发往特定主机的数据包
有时候,我们只想监听发往特定主机的数据包,此时在host
参数上新增dst
即可:1
tcpdump dst host hostname
特定端口
有时候,我们只想监听特定端口往来的数据包,此时使用port
参数指定对应端口即可:1
tcpdump port 3000
监听 TCP/UDP
服务器上不同服务可能分别用了 TCP、UDP 作为传输层,假如只想监听 TCP 或 UDP 的数据包,可以使用以下命令:
1 | tcpdump tcp |
组合用法
来源主机+端口+TCP
可以组合使用前面的命令,比如想监听来自主机123.207.116.169
在端口22
上的 TCP 数据包,可以使用如下命令:
1 | tcpdump tcp port 8088 and src host 42.254.38.55 |
监听特定主机之间的通信
1 | tcpdump ip host 210.27.48.1 and 210.27.48.2 |
210.27.48.1
除了和210.27.48.2
之外的主机之间的通信
1 | tcpdump ip host 210.27.48.1 and ! 210.27.48.2 |
输出信息
tcpdump
总的的输出格式为:系统时间 来源主机.端口 > 目标主机.端口 数据包参数
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22tcpdump host 120.76.246.190
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:03:29.143020 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [S], seq 2060160299, win 29200, options [mss 1460,sackOK,TS val 2563391267 ecr 0,nop,wscale 7], length 0
11:03:29.178998 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [S.], seq 322502808, ack 2060160300, win 28960, options [mss 1424,sackOK,TS val 3801373792 ecr 2563391267,nop,wscale 7], length 0
11:03:29.179029 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [.], ack 1, win 229, options [nop,nop,TS val 2563391303 ecr 3801373792], length 0
11:03:29.275934 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 1:223, ack 1, win 229, options [nop,nop,TS val 2563391400 ecr 3801373792], length 222
11:03:29.311967 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 223, win 235, options [nop,nop,TS val 3801373925 ecr 2563391400], length 0
11:03:29.313341 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 1:3196, ack 223, win 235, options [nop,nop,TS val 3801373926 ecr 2563391400], length 3195
11:03:29.313361 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [.], ack 3196, win 279, options [nop,nop,TS val 2563391438 ecr 3801373926], length 0
11:03:29.316801 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 223:349, ack 3196, win 279, options [nop,nop,TS val 2563391441 ecr 3801373926], length 126
11:03:29.352906 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3196:3247, ack 349, win 235, options [nop,nop,TS val 3801373966 ecr 2563391441], length 51
11:03:29.353136 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 349:1363, ack 3247, win 279, options [nop,nop,TS val 2563391477 ecr 3801373966], length 1014
11:03:29.444488 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 1363, win 251, options [nop,nop,TS val 3801374058 ecr 2563391477], length 0
11:03:29.590307 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3247:3790, ack 1363, win 251, options [nop,nop,TS val 3801374203 ecr 2563391477], length 543
11:03:29.590505 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [P.], seq 1363:1394, ack 3790, win 301, options [nop,nop,TS val 2563391715 ecr 3801374203], length 31
11:03:29.590524 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [F.], seq 1394, ack 3790, win 301, options [nop,nop,TS val 2563391715 ecr 3801374203], length 0
11:03:29.626439 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [.], ack 1394, win 251, options [nop,nop,TS val 3801374239 ecr 2563391715], length 0
11:03:29.626483 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [P.], seq 3790:3821, ack 1395, win 251, options [nop,nop,TS val 3801374240 ecr 2563391715], length 31
11:03:29.626500 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [R], seq 2060161694, win 0, length 0
11:03:29.626511 IP 120.76.246.190.https > VM-0-12-centos.55650: Flags [F.], seq 3821, ack 1395, win 251, options [nop,nop,TS val 3801374240 ecr 2563391715], length 0
11:03:29.626515 IP VM-0-12-centos.55650 > 120.76.246.190.https: Flags [R], seq 2060161694, win 0, length 0
上面输出信息的第一条数据,各部分内容含义:
- 源地址主机(IP)是
VM-0-12-centos
,源端口是55650
- 目的地址是 120.76.246.190,目的端口是 443(
https
端口) >
符号代表数据的方向- 具体报文
对于上面的报文,包含了 TCP 协议的三次握手过程,下面是常见的 TCP 报文的 Flags:
[S]
: SYN(开始连接)[.]
: 没有 Flag[P]
: PSH(推送数据)[F]
: FIN (结束连接)[R]
: RST(重置连接)
参数介绍
ip/icmp/arp/rarp/tcp/udp/icmp
:这些协议可以放到第一个参数的位置,用来指定报文的协议类型-t
:不显示时间戳-c 100
:只抓取100个数据包-w ./target.cap
: 保存成cap文件,方便用其他抓包工具,如 wireshark 分析
保存到本地
备注:tcpdump默认会将输出写到缓冲区,只有缓冲区内容达到一定的大小,或者tcpdump退出时,才会将输出写到本地磁盘
1 | tcpdump -n -vvv -c 1000 -w /tmp/tcpdump_save.cap |
扩展——Wireshark 分析 tcpdump 文件
通常情况下,使用客户端的抓包工具,比如Wireshark
,会比 tcpdump 更容易分析应用层协议。
那么,如何用Wireshark
来分析tcpdump
输出相关报文呢?
一般的做法是这样:在远程服务器上先使用 tcpdump
抓取数据并写入文件,然后再将文件拷贝到本地,最后借助 Wireshark
分析。
其实,还有一种更高效的方法,我们可以直接通过ssh
连接将抓取到的数据实时发送给Wireshark
进行分析。
以 MacOS 系统为例,可以通过 brew cask install wireshark
来安装Wireshark
,然后通过下面的命令来分析:
1 | ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i |
如果想分析 DNS 协议,则可以使用下面的命令:
1 | ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i |
实战
一个比较常见的部署方式:
- 在服务器上部署了 Java 应用,应用使用
8080
端口 - 在服务器上部署了 Nginx,Nginx 反向代理监听
80
端口,将外部的接口请求转发给 Java 应用(127.0.0.1:8080
)。
假设外部调用方向调用这边服务器上的 Java 应用接口,那么此时调用关系如下:
外部调用方 -> Nginx 反向代理 -> Java Server
场景
假设外部调用方(IP地址:202.14.122.107
)访问服务器(IP地址:42.36.146.33
),发现请求出现了异常的结果,那么,此问题该如何排查呢?
分析
其实,这个问题核心就两点:
- 确定调用方数据包有没有发出去
- 确定我方服务器有没有接收来自调用方的数据包
解决方案
我们当然可以通过查看各个应用的日志来确认请求是否到达,若各个应用都没有日志,说明对方的数据包应该没有发到服务器。
不过,有时候外部调用方客户技术不行,人都是懵的,我明明调了你接口啊!你怎么说我没调?
这个时候,你就要帮客户分析如何查问题,最通用的做法就是抓包看下!
首先,我们要明确让客户意识到这边是没有接收到数据包的,此时我们可以在自己服务器抓入口包看下。
本方抓包
若想在自己服务器抓某个端口的流量包,首先你想的是使用以下命令:
1 | tcpdump port 8080 |
但是,需要注意的是,当调用 8080 端口后,你会发现没有任何输出,即使 Java 应用已经收到了请求。
为什么呢?
由于 Nginx 转发到的地址是127.0.0.1
,用的不是默认的网卡,因此,此时需要指定特定网卡。
1 | tcpdump port 8080 -i lo |
执行上面命令后,要求客户再次调用下我方服务器接口,若这边控制台没有观测到任何数据,则说明——我方服务器确实没有收到客户的请求包,不是我们这边的问题。
注意
:上述结论的前提是 Nginx 层面没有拦截请求报文,比如限制了请求方报文大小导致不再进行转发。
外方抓包
通过上面的本方抓包,我们确定了问题不在于接收端,因为接收端根本没有接收到数据报文,那么,此时问题很可能出在外部调用方的服务器了。
那么,客户到底有没有数据包发过来呢,是不是被某个地方拦截了?
假设现在的服务调用链路是:客户 A 服务 –> 客户 B 服务 –> 我方 S 服务。
那么很可能,数据包在客户 B 服务截断了,这个时候,可以要求客户在自己的 B 服务器上查看下自己的出口流量,确定 B 服务调用 S 服务的包是否有发出去:
1 | tcpdump dst host 42.36.146.33 |
如果 A 服务调用 B 服务,而 B 服务做了一些限制:比如 B 服务的 Nginx 限制了报文大小,而 A 服务的报文太大,不被允许转发。那么,此时 B 服务不会再去调用 S 服务,上面的命令也观测不到任何结果了。
这代表,客户自己的服务出问题了,要求客户自己去解决去。
文章信息
时间 | 说明 |
---|---|
2022-09-06 | 初稿 |